//-----------------------------------------------------------------------------
// Filename: SIPTransport.cs
//
// Description: SIP transport layer implementation. Handles different network
// transport options, retransmits, timeouts and transaction matching.
// 
// History:
// 14 Feb 2006	Aaron Clauson	Created.
// 26 Apr 2008  Aaron Clauson   Added TCP support.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006-2008 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BlueFace.Sys;
using BlueFace.Sys.Net;
using BlueFace.VoIP.Net;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace BlueFace.VoIP.Net.SIP
{
    public delegate void SIPTransportRequestReceivedDelegate(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPRequest sipRequest);
    public delegate void SIPTransportResponseReceivedDelegate(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPResponse sipResponse);
    public delegate void SIPTransportSIPRequestInTraceDelegate(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPRequest sipRequest);
    public delegate void SIPTransportSIPRequestOutTraceDelegate(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPRequest sipRequest);
    public delegate void SIPTransportSIPResponseInTraceDelegate(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPResponse sipResponse);
    public delegate void SIPTransportSIPResponseOutTraceDelegate(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPResponse sipResponse);
    public delegate void SIPTransportSIPBadRequestInTraceDelegate(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, string message, SIPValidationError error);
    public delegate void SIPTransportSIPBadResponseInTraceDelegate(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, string message, SIPResponseParserError error);
    public delegate void STUNRequestReceivedDelegate(IPEndPoint receivedEndPoint, IPEndPoint remoteEndPoint, byte[] buffer, int bufferLength);
    public delegate void UnrecognisedMessageReceivedDelegate(IPEndPoint localEndPoint, IPEndPoint fromEndPoint, byte[] buffer);
    
    /// <summary>
    /// Record number of each type of request received.
    /// </summary>
    public struct SIPTransportMetric
    {
        public const string PACKET_VOLUMES_KEY = "pkts";
        public const string SIPMETHOD_VOLUMES_KEY = "meth";
        public const string TOPTALKERS_VOLUME_KEY = "talk";
        
        public DateTime ReceivedAt;
        public IPEndPoint RemoteEndPoint;
        public SIPMessageTypesEnum SIPMessageType;
        public SIPMethodsEnum SIPMethod;
        public bool STUNRequest;
        public bool UnrecognisedPacket;
        public bool BadSIPMessage;                  // Set to true if the message appeared to be a SIP Message but then couldn't be parsed as one.
        public bool Discard;                        // If true indicates the SIP message was not parsed due to the receive queue being full and was instead discarded.
        public bool TooLarge;                       // If the message is greater than the max accepted length.
        public bool Originated;                     // If true inidcates the SIP message was sent by the transport layer, false means it was received.
        public double ParseDuration;                // Time it took to parse the message in milliseconds.

        public SIPTransportMetric(
            DateTime receivedAt, 
            IPEndPoint remoteEndPoint, 
            SIPMessageTypesEnum sipMessageType, 
            SIPMethodsEnum sipMethod, 
            bool stunRequest, 
            bool unrecognisedPacket, 
            bool badSIPMessage,
            bool discard, 
            bool tooLarge,
            bool originated,
            double parseDuration)
        {
            ReceivedAt = receivedAt;
            RemoteEndPoint = remoteEndPoint;
            SIPMessageType = sipMessageType;
            SIPMethod = sipMethod;
            STUNRequest = stunRequest;
            UnrecognisedPacket = unrecognisedPacket;
            BadSIPMessage = badSIPMessage;
            Discard = discard;
            TooLarge = tooLarge;
            Originated = originated;
            ParseDuration = parseDuration;
        }
    }

    public class SIPTransport
	{
        private const string THREAD_NAME = "siptransport";
        private const string METRICS_THREAD_NAME = "siptransport-metrics";
        private const int TIME_WAIT_FINALRESPONSE = 2000;           // Milliseconds to wait after transmitting the final request on a reliable transmission before timing out the request.
        private const int MAX_QUEUEWAIT_PERIOD = 2000;              // Maximum time to wait to check the message received queue if no events are received.
        private const int PENDINGREQUESTS_CHECK_PERIOD = 500;       // Time between checking the pending requests queue to resend reliable requests that have not been responded to.
        private const int MAX_INMESSAGE_QUEUECOUNT = 5000;          // The maximum number of messages that can be stored in the incoming message queue.
        private const int MAX_RELIABLETRANSMISSIONS_COUNT = 5000;   // The maximum number of messages that can be maintained for reliable transmissions.
        private const int MAX_MEASUREMENTSQUEUE_SIZE = 100000;       // If metrics are being used the maximum size the queue will be allowed to reach after which no more measurements will be accepted.
        private const int METRICS_SAMPLE_PERIOD = 60;                // Sample period in seconds for the metrics queue. 
        
        protected static readonly int m_t1 = SIPTimings.T1;         // SIP Timer T1 in milliseconds.
        protected static readonly int m_t6 = SIPTimings.T6;         // this x T1 is how long a reliable request will have retransmits performed for.

		private static ILog logger = AppSpecificState.logger;
        private static ILog metricsLogger = AppState.GetLogger("siptransportmetrics");

        private bool m_queueIncoming = true;     // Dictates whether the transport later will queue incoming requests for processing on a separate thread of process immediately on the same thread.
                                                 // Most SIP elements with the exception of Stateless Proxies would typically want to queue incoming SIP messages.
        private bool m_useMetrics = false;       // Dictates whether measurements will be taken for top talkers, message parsing time and request methods.
        private Queue<SIPTransportMetric> m_sipTransportMeasurements = new Queue<SIPTransportMetric>();
        private bool m_metricsThreadStarted = false;

        private bool m_transportThreadStarted = false;
		private Queue<IncomingMessage> m_inMessageQueue = new Queue<IncomingMessage>();
		private ManualResetEvent m_inMessageArrived = new ManualResetEvent(false);
        private ManualResetEvent m_stopMetrics = new ManualResetEvent(false);
		private bool m_closed = false;

        private List<SIPChannel> m_sipChannels = new List<SIPChannel>();    // List of the physical channels that have been opened and are under management by this instance.
        private List<SIPURI> m_sipLocalURIs = new List<SIPURI>();           // Informational list that identifies the SIP URIs that correspond to local SIP channels.

        private SIPTransactionEngine m_transactionEngine;

        public event SIPTransportRequestReceivedDelegate SIPTransportRequestReceived;
        public event SIPTransportResponseReceivedDelegate SIPTransportResponseReceived;
        public event STUNRequestReceivedDelegate STUNRequestReceived;

        #region Logging and metrics.

        public event SIPTransportSIPRequestInTraceDelegate SIPRequestInTraceEvent;
        public event SIPTransportSIPRequestOutTraceDelegate SIPRequestOutTraceEvent;
        public event SIPTransportSIPResponseInTraceDelegate SIPResponseInTraceEvent;
        public event SIPTransportSIPResponseOutTraceDelegate SIPResponseOutTraceEvent;
        public event SIPTransportSIPBadRequestInTraceDelegate SIPBadRequestInTraceEvent;
        public event SIPTransportSIPBadResponseInTraceDelegate SIPBadResponseInTraceEvent;
        public event UnrecognisedMessageReceivedDelegate UnrecognisedMessageReceived;

        #endregion

        // Contains a list of the SIP Requests/Response that are being monitored or responses and retransmitted on when none is recieved to attempt a more reliable delivery
        // rather then just relying on the initial request to get through.
        private Dictionary<string, SIPTransaction> m_reliableTransmissions = new Dictionary<string, SIPTransaction>();
        private bool m_reliablesThreadRunning = false;   // Only gets started when a request is made to send a reliable request.

        public int ReliableTrasmissionsCount
        {
            get { return m_reliableTransmissions.Count; }
        }

        public IPEndPoint SIPChannelEndPoint
        {
            get { return m_sipChannels[0].SIPChannelEndPoint; }
        }

        public SIPTransport(SIPTransactionEngine transactionEngine)
        {
            m_transactionEngine = transactionEngine;
        }

        public SIPTransport(SIPTransactionEngine transactionEngine, bool queueIncoming, bool useMetrics)
        {
            m_transactionEngine = transactionEngine;
            m_queueIncoming = queueIncoming;
            m_useMetrics = useMetrics;
        }

        public SIPTransport(SIPTransactionEngine transactionEngine, IPEndPoint localEndPoint, bool queueIncoming, bool useMetrics)
		{
            m_transactionEngine = transactionEngine;
            AddSIPChannel(localEndPoint);

            m_queueIncoming = queueIncoming;
            m_useMetrics = useMetrics;

            if (m_queueIncoming)
            {
                StartTransportThread();
            }

            if (m_useMetrics)
            {
                StartMetricsThread();
            }
		}

        /// <summary>
        /// Adds an additional SIP UDP Channel to the transport layer.
        /// </summary>
        /// <param name="localEndPoint"></param>
        public void AddSIPChannel(IPEndPoint localEndPoint)
        {
            SIPChannel sipChannel = new SIPUDPChannel(localEndPoint);
            AddSIPChannel(sipChannel);   
        }

        /// <summary>
        /// Adds an additional SIP Channel to the transport layer.
        /// </summary>
        /// <param name="localEndPoint"></param>
        public void AddSIPChannel(SIPChannel sipChannel)
        {
            try
            {
                bool channelInList = false;
                foreach (SIPChannel existingSIPChannel in m_sipChannels)
                {
                    if (existingSIPChannel.SIPChannelEndPoint.Address.ToString() == sipChannel.SIPChannelEndPoint.Address.ToString() &&
                        existingSIPChannel.SIPChannelEndPoint.Port == sipChannel.SIPChannelEndPoint.Port &&
                        existingSIPChannel.SIPProtocol == sipChannel.SIPProtocol)
                    {
                        channelInList = true;
                        break;
                    }
                }

                if (!channelInList)
                {
                    m_sipChannels.Add(sipChannel);
                    m_sipLocalURIs.Add(new SIPURI(null, sipChannel.SIPChannelEndPoint.ToString(), null, SIPSchemesEnum.sip, sipChannel.SIPProtocol));
                }

                // Wire up the SIP transport to the SIP channel.
                sipChannel.SIPMessageReceived += new SIPMessageReceivedDelegate(ReceiveMessage);

                if (m_queueIncoming && !m_transportThreadStarted)
                {
                    StartTransportThread();
                }

                if (m_useMetrics && !m_metricsThreadStarted)
                {
                    StartMetricsThread();
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception AddSIPChannel. " + excp.Message);
                throw excp;
            }
        }

        private void StartTransportThread()
        {
            if (!m_transportThreadStarted)
            {
                m_transportThreadStarted = true;
                
                Thread inMessageThread = new Thread(new ThreadStart(ProcessInMessage));
                inMessageThread.Priority = ThreadPriority.AboveNormal;
                inMessageThread.Name = THREAD_NAME;
                inMessageThread.Start();
            }
        }

        private void StartMetricsThread()
        {
            if (!m_metricsThreadStarted)
            {
                m_metricsThreadStarted = true;

                Thread metricsThread = new Thread(new ThreadStart(ProcessMetrics));
                metricsThread.Name = METRICS_THREAD_NAME;
                metricsThread.Start();
            }
        }

        private void StartReliableTransmissionsThread()
        {
            m_reliablesThreadRunning = true;

            Thread reliableTransmissionsThread = new Thread(new ThreadStart(ProcessPendingReliableTransactions));
            reliableTransmissionsThread.Name = "siptransport-reliable";
            reliableTransmissionsThread.Start();
        }

		public void ReceiveMessage(SIPChannel sipChannel, IPEndPoint remoteEndPoint, byte[] buffer)
		{
			try
			{ 
                if (!m_queueIncoming)
                {
                    SIPMessageReceived(sipChannel, remoteEndPoint, buffer);
                }
                else
                {
                   IncomingMessage incomingMessage = new IncomingMessage(sipChannel, remoteEndPoint, buffer);

                   // Keep the queue within size limits 
                   if (m_inMessageQueue.Count >= MAX_INMESSAGE_QUEUECOUNT)
                   {
                       logger.Warn("SIPTransport queue full new message from " + remoteEndPoint + " being discarded.");

                       if (m_useMetrics)
                       {
                           RecordDiscardMetric(remoteEndPoint);
                       }

                       //while (m_inMessageQueue.Count >= MAX_INMESSAGE_QUEUECOUNT)
                       //{
                       //    m_inMessageQueue.Dequeue();
                       //}
                   }
                   else
                   {
                       lock (m_inMessageQueue)
                       {
                           m_inMessageQueue.Enqueue(incomingMessage);
                       }
                   }

                   m_inMessageArrived.Set();
                }
			}
			catch(Exception excp)
			{
				logger.Error("Exception SIPTransport ReceiveMessage. " + excp.Message);
				throw excp;
			}
		}
	
		public void Shutdown()
		{
			try
			{
				m_closed = true;

                foreach (SIPChannel channel in m_sipChannels)
                {
                    channel.Close();
                }
	
				m_inMessageArrived.Set();
				m_inMessageArrived.Set();

                m_stopMetrics.Set();

                logger.Debug("SIPTransport Shutdown Complete.");
			}
			catch(Exception excp)
			{
				logger.Error("Exception SIPTransport Shutdown. " + excp.Message);
			}
		}

        public IPEndPoint GetTransportContact(IPEndPoint dstEndPoint)
        {
            return m_sipChannels[0].SIPChannelEndPoint;
        }

        public IPEndPoint GetDefaultTransportContact(SIPProtocolsEnum protocol)
        {
            return GetDefaultChannel(protocol).SIPChannelEndPoint;
        }

        public IPEndPoint[] GetIPEndPointsList()
        {
            IPEndPoint[] endPointsList = new IPEndPoint[] { m_sipChannels[0].SIPChannelEndPoint };

            return endPointsList;
        }

        /// <summary>
        /// Allows raw bytes to be sent from one of the SIPTransport sockets. This should not be used for SIP payloads and instead is
        /// provided to allow other types of payloads to be multi-plexed on the SIP socket. Examples are sending NAT keep alives and
        /// STUN responses where it's useful to use the same socket as the SIP packets.
        /// </summary>
        public void Send(IPEndPoint localEndPoint, IPEndPoint destinationEndPoint, SIPProtocolsEnum protocol, byte[] buffer)
        {
            SIPChannel sendSIPChannel = FindSIPChannelForSocket(localEndPoint, protocol);
            if (sendSIPChannel != null)
            {
                sendSIPChannel.Send(destinationEndPoint, buffer);
            }
            else
            {
                logger.Warn("No SIPChannel could be found for " + localEndPoint + " and " + protocol + " in SIPTransport.Send.");
            }
        }

        public void SendRequest(SIPRequest sipRequest, SIPProtocolsEnum protocol)
        {
            SendRequest(GetDefaultChannel(protocol), sipRequest.GetRequestEndPoint(), sipRequest);
        }

        public void SendRequest(IPEndPoint dstEndPoint, SIPProtocolsEnum protocol, SIPRequest sipRequest)
        {
            SendRequest(GetDefaultChannel(protocol), dstEndPoint, sipRequest);
        }

        public void SendRequestFrom(IPEndPoint localEndPoint, IPEndPoint dstEndPoint, SIPProtocolsEnum protocol, SIPRequest sipRequest)
        {
            SIPChannel sendSIPChannel = FindSIPChannelForSocket(localEndPoint, protocol);
            if(sendSIPChannel == null)
            {
                sendSIPChannel = GetDefaultChannel(protocol);
            }

            SendRequest(sendSIPChannel, dstEndPoint, sipRequest);
        }

        private void SendRequest(SIPChannel sipChannel, IPEndPoint dstEndPoint, SIPRequest sipRequest)
        {
            // These two fields are used for logging.
            sipRequest.ReceivedOn = dstEndPoint;
            sipRequest.ReceivedFrom = sipChannel.SIPChannelEndPoint;

            sipChannel.Send(dstEndPoint, Encoding.ASCII.GetBytes(sipRequest.ToString()), true);

            if (m_useMetrics)
            {
                RecordSIPMessageSendMetric(dstEndPoint, SIPMessageTypesEnum.Request, sipRequest.Method);
            }

            if (SIPRequestOutTraceEvent != null)
            {
                FireSIPRequestOutTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, dstEndPoint, sipRequest);
            }
        }

        /// <summary>
        /// Sends a SIP request/response and keeps track of whether a response/acknowledgement has been received. If no response is received then periodic retransmits are made
        /// for up to T1 x 64 seconds.
        /// </summary>
        public void SendSIPReliable(SIPTransaction sipTransaction)
        {
            if (m_reliableTransmissions.Count >= MAX_RELIABLETRANSMISSIONS_COUNT)
            {
                throw new ApplicationException("Cannot send reliable SIP message as the reliable transmissions queue is full.");
            }

           //logger.Debug("SendSIPReliable transaction URI " + sipTransaction.TransactionRequest.URI.ToString() + ".");
            
            if (sipTransaction.TransactionType == SIPTransactionTypesEnum.Invite &&
                sipTransaction.TransactionState == SIPTransactionStatesEnum.Completed)
            {
                // This is an INVITE transaction that wants to send a reliable response.
                if (sipTransaction.SendFromEndPoint == null)
                {
                    throw new ApplicationException("The SIPTransport layer cannot send a reliable SIP response because the send from socket has not been set for the transaction.");
                }
                else
                {
                    SIPViaHeader topViaHeader = sipTransaction.TransactionFinalResponse.Header.Via.TopViaHeader;
                    SendResponseFrom(sipTransaction.SendFromEndPoint, topViaHeader.GetEndPoint(), topViaHeader.Transport, sipTransaction.TransactionFinalResponse);
                }
            }
            else
            {
                if (sipTransaction.RemoteEndPoint == null)
                {
                    // Don't allow this as it can result in additional DNS lookups which should be avoided. Whatever created the transaction must set the
                    // RemoteEndPoint prior to requesting reliable transmission.
                    throw new ApplicationException("The SIPTransport layer cannot send a reliable SIP request because the destination socket has not been set for the transaction.");
                }
                else if (sipTransaction.SendFromEndPoint == null)
                {
                    throw new ApplicationException("The SIPTransport layer cannot send a reliable SIP request because the send from socket has not been set for the transaction.");
                }
                else
                {
                    SendRequestFrom(sipTransaction.SendFromEndPoint, sipTransaction.RemoteEndPoint, sipTransaction.Protocol, sipTransaction.TransactionRequest);
                }
            }

            sipTransaction.Retransmits = 1;
            sipTransaction.InitialTransmit = DateTime.Now;
            sipTransaction.LastTransmit = DateTime.Now;
            sipTransaction.DeliveryPending = true;

            if (!m_reliableTransmissions.ContainsKey(sipTransaction.TransactionId))
            {
                lock (m_reliableTransmissions)
                {
                    m_reliableTransmissions.Add(sipTransaction.TransactionId, sipTransaction);
                }
            }

            if (!m_reliablesThreadRunning)
            {
                StartReliableTransmissionsThread();
            }
        }

        public void SendResponse(SIPResponse sipResponse)
        {
            SIPViaHeader topViaHeader = sipResponse.Header.Via.TopViaHeader;
            SIPChannel sipChannel = GetDefaultChannel(topViaHeader.Transport);

            if (sipChannel != null)
            {
                SendResponse(sipChannel, topViaHeader.GetEndPoint(), sipResponse);
            }
            else
            {
                logger.Warn("Could not find channel to send SIP Response in SendResponse: " + topViaHeader.ToString() + ".");
            }
        }

        public void SendResponseFrom(IPEndPoint localEndPoint, SIPResponse sipResponse)
        {
            SIPViaHeader topViaHeader = sipResponse.Header.Via.TopViaHeader;
            SendResponseFrom(localEndPoint, topViaHeader.GetEndPoint(), topViaHeader.Transport, sipResponse);
        }

        /// <summary>
        /// Should only be used when the top Via header on the SIPResponse is dubious. For example if an invalid SIPRequest is received
        /// and the Via header is invalid then the response should be sent to the socket the request was received on.
        /// </summary>
        public void SendResponseFrom(IPEndPoint localEndPoint, IPEndPoint dstEndPoint, SIPProtocolsEnum protocol, SIPResponse sipResponse)
        {
            SIPChannel sendSIPChannel = FindSIPChannelForSocket(localEndPoint, protocol);
            if (sendSIPChannel == null)
            {
                sendSIPChannel = GetDefaultChannel(sipResponse.Header.Via.TopViaHeader.Transport);
            }

            if (sendSIPChannel != null)
            {
                SendResponse(sendSIPChannel, dstEndPoint, sipResponse);
            }
            else
            {
                logger.Warn("Could not find channel to send SIP Response in SendResponseFrom: " + sipResponse.Header.Via.TopViaHeader.ToString() + ".");
            }
        }

        private void SendResponse(SIPChannel sipChannel, SIPResponse sipResponse)
        {
            SendResponse(sipChannel, sipResponse.Header.Via.TopViaHeader.GetEndPoint(), sipResponse);
        }

        private void SendResponse(SIPChannel sipChannel, IPEndPoint dstEndPoint, SIPResponse sipResponse)
        {
            sipChannel.Send(dstEndPoint, Encoding.UTF8.GetBytes(sipResponse.ToString()), false);

            if (m_useMetrics)
            {
                RecordSIPMessageSendMetric(dstEndPoint, SIPMessageTypesEnum.Response, sipResponse.Header.CSeqMethod);
            }

            if (SIPRequestOutTraceEvent != null)
            {
                FireSIPResponseOutTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, dstEndPoint, sipResponse);
            }
        }

		private void ProcessInMessage()
		{
			try
			{			
				while(!m_closed)
				{
                    m_transactionEngine.RemoveExpiredTransactions();
                    
                    while(m_inMessageQueue.Count > 0)
					{
						IncomingMessage incomingMessage = null;
                        						
						lock(m_inMessageQueue)
						{
							incomingMessage = m_inMessageQueue.Dequeue();
						}

						if(incomingMessage != null)
						{
                            SIPMessageReceived(incomingMessage.InSIPChannel, incomingMessage.InEndPoint, incomingMessage.Buffer);
						}
					}

					m_inMessageArrived.Reset();
					m_inMessageArrived.WaitOne(MAX_QUEUEWAIT_PERIOD, false);
				}
			}
			catch(Exception excp)
			{
				logger.Error("Exception SIPTransport ProcessInMessage. " + excp.Message);
			}
		}

        private void ProcessMetrics()
        {
            try
            {
                logger.Debug("SIPTransport ProcessMetrics thread started.");
                
                while (!m_closed)
                {
                    string sampleTimeString = DateTime.Now.ToString("dd MMM yyyy HH:mm:ss");
                    
                    if (m_sipTransportMeasurements.Count > 0)
                    {
                        lock (m_sipTransportMeasurements)
                        {
                            // Remove all samples older than current sample period.
                            while (m_sipTransportMeasurements.Count > 0 && DateTime.Now.Subtract(m_sipTransportMeasurements.Peek().ReceivedAt).TotalSeconds > METRICS_SAMPLE_PERIOD)
                            {
                                m_sipTransportMeasurements.Dequeue();
                            }

                            if (m_sipTransportMeasurements.Count > 0)
                            {
                                #region Collate samples into single measurement.

                                // Take sample.
                                DateTime startSampleDate = DateTime.Now.AddSeconds(-1 * METRICS_SAMPLE_PERIOD);
                                DateTime endSampleDate = DateTime.Now;
                                int totalPackets = 0;
                                double totalParsedPackets = 0;
                                double totalParseTime = 0;
                                int sipMessageCount = 0;
                                int sipRequestCount = 0;
                                int sipResponseCount = 0;
                                int sipRequestSentCount = 0;
                                int sipResponseSentCount = 0;
                                int discardsCount = 0;
                                int badSIPCount = 0;
                                int tooLargeCount = 0;
                                int stunRequestsCount = 0;
                                int unrecognisedCount = 0;
                                Dictionary<string, int> topTalkers = new Dictionary<string, int>();
                                Dictionary<SIPMethodsEnum, int> sipMessageTypes = new Dictionary<SIPMethodsEnum, int>();

                                SIPTransportMetric[] measurements = m_sipTransportMeasurements.ToArray();
                                foreach (SIPTransportMetric measurement in measurements)
                                {
                                    totalPackets++;

                                    if (measurement.RemoteEndPoint != null)
                                    {
                                        string talker = measurement.RemoteEndPoint.ToString();
                                        if(topTalkers.ContainsKey(talker))
                                        {
                                            topTalkers[talker] = topTalkers[talker] + 1;
                                        }
                                        else
                                        {
                                            topTalkers.Add(talker, 1);
                                        }
                                    }

                                    if (!measurement.Originated && !measurement.Discard)
                                    {
                                        totalParsedPackets++;
                                        totalParseTime += measurement.ParseDuration;
                                    }

                                    if (measurement.Discard)
                                    {
                                        discardsCount++;
                                    }
                                    else if (measurement.BadSIPMessage)
                                    {
                                        badSIPCount++;
                                    }
                                    else if (measurement.UnrecognisedPacket)
                                    {
                                        unrecognisedCount++;
                                    }
                                    else if (measurement.STUNRequest)
                                    {
                                        stunRequestsCount++;
                                    }
                                    else if (measurement.TooLarge)
                                    {
                                        tooLargeCount++;
                                    }
                                    else
                                    {
                                        sipMessageCount++;

                                        if (sipMessageTypes.ContainsKey(measurement.SIPMethod))
                                        {
                                            sipMessageTypes[measurement.SIPMethod] = sipMessageTypes[measurement.SIPMethod] + 1;
                                        }
                                        else
                                        {
                                            sipMessageTypes.Add(measurement.SIPMethod, 1);
                                        }

                                        if (measurement.SIPMessageType == SIPMessageTypesEnum.Request)
                                        {
                                            if (measurement.Originated)
                                            {
                                                sipRequestSentCount++;
                                            }
                                            else
                                            {
                                                sipRequestCount++;
                                            }
                                        }
                                        else
                                        {
                                            if (measurement.Originated)
                                            {
                                                sipResponseSentCount++;
                                            }
                                            else
                                            {
                                                sipResponseCount++;
                                            }
                                        }
                                    }
                                }

                                #endregion
                                
                                double avgParseTime = 0;
                                if (totalParsedPackets > 0)
                                {
                                    avgParseTime = totalParseTime / totalParsedPackets;
                                }

                                metricsLogger.Info(
                                    SIPTransportMetric.PACKET_VOLUMES_KEY + "," +
                                    sampleTimeString + "," +
                                    METRICS_SAMPLE_PERIOD + "," +
                                    totalPackets + "," +
                                    sipRequestCount + "," +
                                    sipResponseCount + "," +
                                    sipRequestSentCount + "," +
                                    sipResponseSentCount + "," +
                                    //SIPTransaction.Count + "," +
                                    unrecognisedCount + "," +
                                    badSIPCount + "," +
                                    stunRequestsCount + "," +
                                    discardsCount + "," +
                                    tooLargeCount + "," +
                                    totalParseTime.ToString("0.###") + "," +
                                    avgParseTime.ToString("0.###"));

                                #region Build SIP methods metric string.

                                if (sipMessageTypes.Count > 0)
                                {
                                    string methodCountStr = SIPTransportMetric.SIPMETHOD_VOLUMES_KEY + "," + sampleTimeString  + "," + METRICS_SAMPLE_PERIOD;
                                    foreach (KeyValuePair<SIPMethodsEnum, int> methodCount in sipMessageTypes)
                                    {
                                        methodCountStr += "," + methodCount.Key + "=" + methodCount.Value;
                                    }
                                    metricsLogger.Info(methodCountStr);
                                }
                                else
                                {
                                    metricsLogger.Info(SIPTransportMetric.SIPMETHOD_VOLUMES_KEY + "," + sampleTimeString + "," + METRICS_SAMPLE_PERIOD);
                                }

                                #endregion

                                #region Build top talkers metric string.

                                if (topTalkers.Count > 0)
                                {
                                    string topTalkersStr = SIPTransportMetric.TOPTALKERS_VOLUME_KEY + "," + sampleTimeString + "," + METRICS_SAMPLE_PERIOD;

                                    for (int index = 0; index < 10; index++)
                                    {
                                        if (topTalkers.Count == 0)
                                        {
                                            break;
                                        }

                                        string curTopTalker = null;
                                        int curTopTalkerCount = 0;
                                        foreach (KeyValuePair<string, int> topTalker in topTalkers)
                                        {
                                            if (topTalker.Value > curTopTalkerCount)
                                            {
                                                curTopTalker = topTalker.Key;
                                                curTopTalkerCount = topTalker.Value;
                                            }
                                        }
                                        topTalkersStr += "," + curTopTalker + "=" + curTopTalkerCount;
                                        topTalkers.Remove(curTopTalker);
                                    }
                                    metricsLogger.Info(topTalkersStr);
                                }
                                else
                                {
                                    metricsLogger.Info(SIPTransportMetric.TOPTALKERS_VOLUME_KEY + "," + sampleTimeString + "," + METRICS_SAMPLE_PERIOD);
                                }

                                #endregion
                            }
                            else
                            {
                                //metricsLogger.Info(SIPTransportMetric.PACKET_VOLUMES_KEY + "," + sampleTimeString + "," + METRICS_SAMPLE_PERIOD + ",0,0,0,0,0,0," + SIPTransaction.Count + ",0,0,0,0,0,0");
                                metricsLogger.Info(SIPTransportMetric.SIPMETHOD_VOLUMES_KEY + "," + sampleTimeString + "," + METRICS_SAMPLE_PERIOD);
                                metricsLogger.Info(SIPTransportMetric.TOPTALKERS_VOLUME_KEY + "," + sampleTimeString + "," + METRICS_SAMPLE_PERIOD);
                            }
                        }
                    }
                    else
                    {
                       // metricsLogger.Info(SIPTransportMetric.PACKET_VOLUMES_KEY + "," + sampleTimeString + "," + METRICS_SAMPLE_PERIOD + ",0,0,0,0,0,0," + SIPTransaction.Count + ",0,0,0,0,0,0");
                        metricsLogger.Info(SIPTransportMetric.SIPMETHOD_VOLUMES_KEY + "," + sampleTimeString + "," + METRICS_SAMPLE_PERIOD);
                        metricsLogger.Info(SIPTransportMetric.TOPTALKERS_VOLUME_KEY + "," + sampleTimeString + "," + METRICS_SAMPLE_PERIOD);
                    }

                    m_stopMetrics.WaitOne(METRICS_SAMPLE_PERIOD * 1000, false);
                }

                logger.Debug("SIPTransport ProcessMetrics thread stopped.");
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport ProcessMetrics. " + excp.Message);
            }
        }

        private void ProcessPendingReliableTransactions()
        {
            try
            {
                m_reliablesThreadRunning = true;

                while (!m_closed)
                {
                    if (m_reliableTransmissions.Count == 0)
                    {
                        // No request retransmissions in progress close down thread until next one required.
                        m_reliablesThreadRunning = false;
                        break;
                    }

                    try
                    {
                        List<string> deliveredTransactions = new List<string>();

                        lock (m_reliableTransmissions)
                        {
                            foreach (SIPTransaction transaction in m_reliableTransmissions.Values)
                            {
                                if (!transaction.DeliveryPending)
                                {
                                    deliveredTransactions.Add(transaction.TransactionId);
                                }
                                else if(transaction.TransactionState == SIPTransactionStatesEnum.Terminated || transaction.TransactionState == SIPTransactionStatesEnum.Confirmed || transaction.HasTimedOut)
                                {
                                    deliveredTransactions.Add(transaction.TransactionId);
                                }
                                else
                                {
                                    if (DateTime.Now.Subtract(transaction.InitialTransmit).TotalMilliseconds > (m_t6 * 2 + TIME_WAIT_FINALRESPONSE))
                                    {
                                        logger.Debug("Request timed out " + transaction.TransactionRequest.Method + " " + transaction.TransactionRequest.URI.ToString() + ".");

                                        // Transaction timeout event will be fired by the transaction class so do not fire here.
                                        transaction.DeliveryFailed = true;
                                        deliveredTransactions.Add(transaction.TransactionId);
                                    }
                                    else
                                    {
                                        double nextTransmitMilliseconds = Math.Pow(2, transaction.Retransmits - 1) * m_t1;
                                        //logger.Debug("Time since retransmit " + transaction .RequestTransmits + " for " + transaction.InitialRequest.Method + " " + transaction.InitialRequest.URI.ToString() + " " + DateTime.Now.Subtract(transaction.LastRequestTransmit).TotalMilliseconds + ".");

                                        if (DateTime.Now.Subtract(transaction.LastTransmit).TotalMilliseconds >= nextTransmitMilliseconds)
                                        {
                                            transaction.Retransmits = transaction.Retransmits + 1;
                                            transaction.LastTransmit = DateTime.Now;

                                            if (transaction.TransactionType == SIPTransactionTypesEnum.Invite && transaction.TransactionState == SIPTransactionStatesEnum.Completed)
                                            {
                                                //logger.Debug("Retransmit " + transaction.Retransmits + "(" + transaction.TransactionId + ") for INVITE reponse " + transaction.TransactionRequest.URI.ToString() + ", last=" + DateTime.Now.Subtract(transaction.LastTransmit).TotalMilliseconds + "ms, first=" + DateTime.Now.Subtract(transaction.InitialTransmit).TotalMilliseconds + "ms.");

                                                // This is an INVITE transaction that wants to send a reliable response, once the ACK is received it will change the transaction state to confirmed.
                                                SIPViaHeader topViaHeader = transaction.TransactionFinalResponse.Header.Via.TopViaHeader;
                                                SendResponseFrom(transaction.SendFromEndPoint, topViaHeader.GetEndPoint(), topViaHeader.Transport, transaction.TransactionFinalResponse);
                                                transaction.ResponseRetransmit();
                                            }
                                            else
                                            {
                                                //logger.Debug("Retransmit " + transaction.Retransmits + " for request " + transaction.TransactionRequest.Method + " " + transaction.TransactionRequest.URI.ToString() + ", last=" + DateTime.Now.Subtract(transaction.LastTransmit).TotalMilliseconds + "ms, first=" + DateTime.Now.Subtract(transaction.InitialTransmit).TotalMilliseconds + "ms.");
                                                SendRequestFrom(transaction.SendFromEndPoint, transaction.RemoteEndPoint, transaction.Protocol, transaction.TransactionRequest);
                                                transaction.RequestRetransmit();
                                            }
                                        }
                                    }
                                }
                            }

                            // Remove timed out or complete transactions from reliable transmissions list.
                            if (deliveredTransactions.Count > 0)
                            {
                                foreach (string transactionId in deliveredTransactions)
                                {
                                    if (m_reliableTransmissions.ContainsKey(transactionId))
                                    {
                                        m_reliableTransmissions.Remove(transactionId);
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception excp)
                    {
                        logger.Error("Exception SIPTransport ProcessPendingRequests checking pendings. " + excp.Message);
                    }

                    Thread.Sleep(PENDINGREQUESTS_CHECK_PERIOD);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport ProcessPendingRequests. " + excp.Message);
            }
            finally
            {
                m_reliablesThreadRunning = false;
            }
        }
	
		private void SIPMessageReceived(SIPChannel sipChannel, IPEndPoint remoteEndPoint, byte[] buffer)
		{
			try
			{
                DateTime startParseTime = DateTime.Now;

                if (buffer != null && buffer.Length > 0)
                {
                    if ((buffer[0] == 0x0 || buffer[0] == 0x1) && buffer.Length >= 20)
                    {                       
                        // Treat any messages that cannot be SIP as STUN requests.
                        if (STUNRequestReceived != null)
                        {
                            STUNRequestReceived(sipChannel.SIPChannelEndPoint, remoteEndPoint, buffer, buffer.Length);
                        }

                        if (m_useMetrics)
                        {
                            RecordSTUNMessageMetric(remoteEndPoint, startParseTime);
                        }
                    }
                    else
                    {
                        // Treat all messages that don't match STUN requests as SIP.
                        if (buffer.Length > SIPConstants.SIP_MAXIMUM_LENGTH)
                        {
                            //logger.Warn("SIPTransport dropping Excessive Length SIP Message " + buffer.Length + " bytes from " + IPSocket.GetSocketString(remoteEndPoint) + ".");
                            FireSIPBadResponseInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, null, SIPResponseParserError.TooLarge);
                            SIPResponse tooLargeResponse = GetResponse(null, SIPResponseStatusCodesEnum.MessageTooLarge, null, sipChannel.SIPChannelEndPoint, remoteEndPoint);
                            SendResponse(tooLargeResponse);

                            if (m_useMetrics)
                            {
                                RecordTooLargeMessageMetric(remoteEndPoint, startParseTime);
                            }
                        }
                        else
                        {
                            SIPMessage sipMessage = SIPMessage.ParseSIPMessage(buffer, remoteEndPoint, sipChannel.SIPChannelEndPoint);

                            if (sipMessage != null)
                            {
                                if (sipMessage.SIPMessageType == SIPMessageTypesEnum.Response)
                                {
                                    #region SIP Response.

                                    SIPResponseParserError responseParserError = SIPResponseParserError.None;
                                    SIPResponse sipResponse = SIPResponse.ParseSIPResponse(sipMessage, out responseParserError);
                                    sipResponse.ReceivedFrom = remoteEndPoint;
                                    sipResponse.ReceivedOn = sipChannel.SIPChannelEndPoint;

                                    if (sipResponse != null && responseParserError == SIPResponseParserError.None)
                                    {
                                        if (sipResponse.Valid)
                                        {
                                            if (SIPResponseInTraceEvent != null)
                                            {
                                                FireSIPResponseInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, sipResponse);
                                            }

                                            if (m_transactionEngine != null && m_transactionEngine.Exists(sipResponse))
                                            {
                                                SIPTransaction transaction = m_transactionEngine.GetTransaction(sipResponse);
                                               
                                                if (transaction.TransactionState != SIPTransactionStatesEnum.Completed)
                                                {
                                                    transaction.DeliveryPending = false;
                                                    if (m_reliableTransmissions.ContainsKey(transaction.TransactionId))
                                                    {
                                                        lock (m_reliableTransmissions)
                                                        {
                                                            m_reliableTransmissions.Remove(transaction.TransactionId);
                                                        }
                                                    }  
                                                }

                                                transaction.GotResponse(sipChannel.SIPChannelEndPoint, remoteEndPoint, sipResponse);
                                            }
                                            else if (SIPTransportResponseReceived != null)
                                            {
                                                SIPTransportResponseReceived(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, sipResponse);
                                            }

                                            if (m_useMetrics)
                                            {
                                                RecordSIPMessageMetric(remoteEndPoint, SIPMessageTypesEnum.Response, sipResponse.Header.CSeqMethod, startParseTime);
                                            }
                                        }
                                        else
                                        {
                                            //logger.Warn("Invalid SIP response from " + sipMessage.ReceivedFrom + ", " + sipResponse.ValidationError + " , ignoring.");
                                            //logger.Warn(sipMessage.RawMessage);
                                            FireSIPBadResponseInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, sipMessage.RawMessage, responseParserError);

                                            if (m_useMetrics)
                                            {
                                                RecordBadSIPMessageMetric(remoteEndPoint, startParseTime);
                                            }
                                        }
                                    }
                                    else
                                    {
                                        //logger.Warn("Invalid SIP response from " + remoteEndPoint + ", " + responseParserError + ", ignoring.");
                                        //logger.Warn(sipMessage.RawMessage);
                                        FireSIPBadResponseInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, sipMessage.RawMessage, responseParserError);

                                        if (m_useMetrics)
                                        {
                                            RecordBadSIPMessageMetric(remoteEndPoint, startParseTime);
                                        }
                                    }

                                    #endregion
                                }
                                else
                                {
                                    #region SIP Request.

                                    SIPValidationError requestParserError = SIPValidationError.None;
                                    SIPRequest sipRequest = SIPRequest.ParseSIPRequest(sipMessage, out requestParserError);
                                    sipRequest.ReceivedFrom = remoteEndPoint;
                                    sipRequest.ReceivedOn = sipChannel.SIPChannelEndPoint;

                                    if (sipRequest != null && sipRequest.Valid)
                                    {
                                        if (SIPRequestInTraceEvent != null)
                                        {
                                            FireSIPRequestInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, sipRequest);
                                        }

                                        // Stateful cores will create transactions once they get the request and the transport layer will use those transactions.
                                        // Stateless cores will not be affected by this step as the transaction layer will always return false.
                                        SIPTransaction requestTransaction = (m_transactionEngine != null) ? m_transactionEngine.GetTransaction(sipRequest) : null;
                                        if (requestTransaction != null)
                                        {
                                            if (requestTransaction.TransactionState == SIPTransactionStatesEnum.Completed && sipRequest.Method != SIPMethodsEnum.ACK)
                                            {
                                                //logger.Debug("Resending final response for " + sipRequest.Method + " " + sipRequest.URI.ToString() + " from " + IPSocket.GetSocketString(remoteEndPoint) + ".");

                                                SIPResponse finalResponse = requestTransaction.TransactionFinalResponse;
                                                SendResponseFrom(requestTransaction.SendFromEndPoint, finalResponse.Header.Via.TopViaHeader.GetEndPoint(), finalResponse.Header.Via.TopViaHeader.Transport, finalResponse);
                                                requestTransaction.Retransmits += 1;
                                                requestTransaction.LastTransmit = DateTime.Now;
                                                requestTransaction.ResponseRetransmit();
                                            }
                                            else if (sipRequest.Method == SIPMethodsEnum.ACK)
                                            {
                                                //logger.Debug("ACK received for (" + requestTransaction.TransactionId + ") " + requestTransaction.TransactionRequest.URI.ToString() + ".");

                                                if (requestTransaction.TransactionState == SIPTransactionStatesEnum.Completed)
                                                {
                                                    //logger.Debug("ACK received for INVITE, setting state to Confirmed, " + sipRequest.URI.ToString() + " from " + sipRequest.Header.From.FromURI.User + " " + remoteEndPoint + ".");
                                                    //requestTransaction.UpdateTransactionState(SIPTransactionStatesEnum.Confirmed);
                                                    requestTransaction.ACKReceived(sipChannel.SIPChannelEndPoint, remoteEndPoint, sipRequest);
                                                }
                                                else
                                                {
                                                    //logger.Debug("ACK recieved on " + requestTransaction.TransactionState + " transaction ignoring.");
                                                    FireSIPBadRequestInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, "ACK recieved on " + requestTransaction.TransactionState + " transaction ignoring.", SIPValidationError.AckOnNonCompletedTransaction);
                                                }
                                            }
                                            else
                                            {
                                                //logger.Debug("Transaction already exists, ignoring duplicate request, " + sipRequest.Method + " " + sipRequest.URI.ToString() + " from " + sipRequest.Header.From.FromURI.User + " " + IPSocket.GetSocketString(remoteEndPoint) + ".");
                                                FireSIPBadRequestInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, "Transaction already exists, ignoring duplicate request, " + sipRequest.Method + " " + sipRequest.URI.ToString() + " from " + sipRequest.Header.From.FromURI.User + " " + IPSocket.GetSocketString(remoteEndPoint) + ".", SIPValidationError.DuplicateTransaction);
                                            }
                                        }
                                        else if (SIPTransportRequestReceived != null)
                                        {
                                            // This is a new SIP request and if the validity checks are passed it will be handed off to all subscribed new request listeners.

                                            #region Check for invalid SIP requests.

                                            if (sipRequest.Header.MaxForwards == 0 && sipRequest.Method != SIPMethodsEnum.OPTIONS)
                                            {
                                                // Check the MaxForwards value, if equal to 0 the request must be discarded. If MaxForwards is -1 it indicates the
                                                // header was not present in the request and that the MaxForwards check should not be undertaken.
                                                //logger.Warn("SIPTransport responding with TooManyHops due to 0 MaxForwards.");
                                                FireSIPBadRequestInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, "Zero MaxForwards on " + sipRequest.Method + " " + sipRequest.URI.ToString() + " from " + sipRequest.Header.From.FromURI.User + " " + IPSocket.GetSocketString(remoteEndPoint), SIPValidationError.TooManyHops);
                                                SIPResponse tooManyHops = GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.TooManyHops, null, sipChannel.SIPChannelEndPoint, remoteEndPoint);
                                                SendResponse(sipChannel, tooManyHops);
                                                return;
                                            }
                                            else if (sipRequest.IsLoop(sipChannel.SIPChannelEndPoint.Address.ToString(), sipChannel.SIPChannelEndPoint.Port, sipRequest.CreateBranchId()))
                                            {
                                                //logger.Warn("SIPTransport Dropping looped request.");
                                                FireSIPBadRequestInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, "Dropping looped request, " + sipRequest.Method + " " + sipRequest.URI.ToString() + " from " + sipRequest.Header.From.FromURI.User + " " + IPSocket.GetSocketString(remoteEndPoint), SIPValidationError.Loop);
                                                SIPResponse loopResponse = GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.LoopDetected, null, sipChannel.SIPChannelEndPoint, remoteEndPoint);
                                                SendResponse(loopResponse);
                                                return;
                                            }

                                            #endregion

                                            #region Route pre-processing.

                                            if (sipRequest.Header.Routes.Length > 0)
                                            {
                                                sipRequest.PreProcessRouteInfo(m_sipLocalURIs);
                                            }

                                            #endregion

                                            // Request has passed validity checks, adjust the client Via header to reflect the socket the request was received on.
                                            //SIPViaHeader originalTopViaHeader = sipRequest.Header.Via.TopViaHeader;
                                            sipRequest.Header.Via.UpateTopViaHeader(remoteEndPoint);

                                            // Stateful cores should create a transaction once they receive this event, stateless cores should not.
                                            SIPTransportRequestReceived(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, sipRequest);
                                        }

                                        if (m_useMetrics)
                                        {
                                            RecordSIPMessageMetric(remoteEndPoint, SIPMessageTypesEnum.Request, sipRequest.Header.CSeqMethod, startParseTime);
                                        }
                                    }
                                    else
                                    {
                                        //logger.Warn("Invalid SIP request from " + sipMessage.ReceivedFrom + ", parser error, " + requestParserError + ", sending BadRequest error response to " + remoteEndPoint.ToString() + ".");
                                        //logger.Warn(sipMessage.RawMessage);
                                        FireSIPBadRequestInTraceEvent(sipChannel.SIPProtocol, sipChannel.SIPChannelEndPoint, remoteEndPoint, sipMessage.RawMessage, requestParserError);
                                        
                                        if (sipRequest != null)
                                        {
                                            SIPResponse errorResponse = GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.BadRequest, requestParserError.ToString(), sipChannel.SIPChannelEndPoint, remoteEndPoint);
                                            SendResponse(sipChannel, errorResponse);
                                        }

                                        if (m_useMetrics)
                                        {
                                            RecordBadSIPMessageMetric(remoteEndPoint, startParseTime);
                                        }
                                    }

                                    #endregion
                                }
                            }
                            else
                            {
                                if (UnrecognisedMessageReceived != null)
                                {
                                    UnrecognisedMessageReceived(sipChannel.SIPChannelEndPoint, remoteEndPoint, buffer);
                                }

                                if (m_useMetrics)
                                {
                                    RecordUnrecognisedMessageMetric(remoteEndPoint, startParseTime);
                                }
                            }
                        }
                    }
                }
			}
			catch(Exception excp)
			{
				logger.Error("Exception SIPTransport SIPMessageReceived. " + excp.Message);
				//throw excp;
			}
		}

        /// <summary>
        /// Checks the Contact SIP URI host and if it is recognised as a private address it is replaced with the socket
        /// the SIP message was received on.
        /// 
        /// Private address space blocks RFC 1597.
        ///		10.0.0.0        -   10.255.255.255
        ///		172.16.0.0      -   172.31.255.255
        ///		192.168.0.0     -   192.168.255.255
        ///
        /// </summary>
        public static bool IsPrivateAddress(string host)
        {
            if (host != null && host.Trim().Length > 0)
            {
                if (host.StartsWith("127.0.0.1") ||
                    host.StartsWith("10.") ||
                    Regex.Match(host, @"^172\.2\d\.").Success ||
                    host.StartsWith("172.30.") ||
                    host.StartsWith("172.31.") ||
                    host.StartsWith("192.168."))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                logger.Error("Cannot check private address against an empty host.");
                return false;
            }
        }

        public static SIPResponse GetResponse(SIPHeader requestHeader, SIPResponseStatusCodesEnum responseCode, string reasonPhrase, IPEndPoint localEndPoint, IPEndPoint remoteEndPoint)
        {
            try
            {
                SIPResponse response = new SIPResponse(responseCode);

                if (reasonPhrase != null)
                {
                    response.ReasonPhrase = reasonPhrase;
                }

                SIPFromHeader from = (requestHeader == null || requestHeader.From != null) ? requestHeader.From : new SIPFromHeader(null, SIPURI.ParseSIPURI("sip:" + remoteEndPoint.ToString()), null);
                SIPToHeader to = (requestHeader == null || requestHeader.To != null) ? requestHeader.To : new SIPToHeader(null, SIPURI.ParseSIPURI("sip:" + localEndPoint.ToString()), null);
                int cSeq = (requestHeader == null || requestHeader.CSeq != -1) ? requestHeader.CSeq : 1;
                string callId = (requestHeader == null || requestHeader.CallId != null) ? requestHeader.CallId : CallProperties.CreateNewCallId();

                response.Header = new SIPHeader(from, to, cSeq, callId);
                response.Header.CSeqMethod = (requestHeader != null ) ? requestHeader.CSeqMethod : SIPMethodsEnum.NONE;

                if (requestHeader == null || requestHeader.Via == null || requestHeader.Via.Length == 0)
                {
                    response.Header.Via.PushViaHeader(new SIPViaHeader(remoteEndPoint.Address.ToString(), remoteEndPoint.Port, CallProperties.CreateBranchId()));
                }
                else
                {
                    response.Header.Via = requestHeader.Via;
                }

                response.Header.MaxForwards = Int32.MinValue;

                return response;
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport GetResponse. " + excp.Message);
                throw excp;
            }
        }

        public static SIPRequest GetRequest(SIPMethodsEnum method, SIPURI uri, SIPToHeader to, IPEndPoint localEndPoint)
        {
            SIPRequest request = new SIPRequest(method, uri);

            SIPContactHeader contactHeader = SIPContactHeader.ParseContactHeader("sip:" + localEndPoint)[0];
            SIPFromHeader fromHeader = new SIPFromHeader(null, contactHeader.ContactURI, CallProperties.CreateNewTag());
            SIPHeader header = new SIPHeader(fromHeader, to, Crypto.GetRandomInt(1000, 9999), CallProperties.CreateNewCallId());
            request.Header = header;
            header.CSeqMethod = method;
            SIPViaHeader viaHeader = new SIPViaHeader(localEndPoint.Address.ToString(), localEndPoint.Port, CallProperties.CreateBranchId());
            header.Via.PushViaHeader(viaHeader);

            return request;
        }

        /// <summary>
        /// Checks the list of local SIP sockets to determine if the specified one is in the list.
        /// </summary>
        public bool IsLocalSIPSocket(string socket)
        {
            return IsLocalSIPSocket(socket, SIPProtocolsEnum.UDP);
        }

        /// <summary>
        /// Checks the list of local SIP sockets to determine if the specified one is in the list.
        /// </summary>
        public bool IsLocalSIPSocket(string socket, SIPProtocolsEnum protocol)
        {
            foreach (SIPChannel channel in m_sipChannels)
            {
                if (channel.SIPChannelEndPoint.ToString() == socket &&
                    channel.SIPProtocol == protocol)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Attempts to match a SIPChannel for this process that has the specified local end point and protocol.
        /// </summary>
        /// <param name="localEndPoint">The local socket endpoint of the SIPChannel to find.</param>
        /// <param name="protocol">The protocol of the SIPChannel to find.</param>
        /// <returns>A matching SIPChannel if found otherwise null.</returns>
        private SIPChannel FindSIPChannelForSocket(IPEndPoint localEndPoint, SIPProtocolsEnum protocol)
        {
            SIPChannel sendSIPChannel = null;

            // Attempt to find the SIP Channel that has an end point of srcEndPoint.
            foreach (SIPChannel sipChannel in m_sipChannels)
            {
                if (sipChannel.SIPChannelEndPoint.Address.ToString() == localEndPoint.Address.ToString() &&
                    sipChannel.SIPChannelEndPoint.Port == localEndPoint.Port &&
                    sipChannel.SIPProtocol == protocol)
                {
                    sendSIPChannel = sipChannel;
                }
            }

            return sendSIPChannel;
        }

        /// <summary>
        /// Returns the first SIPChannel found for the requested protocol.
        /// </summary>
        /// <param name="protocol"></param>
        /// <returns></returns>
        private SIPChannel GetDefaultChannel(SIPProtocolsEnum protocol)
        {
            foreach (SIPChannel sipChannel in m_sipChannels)
            {
                if (sipChannel.SIPProtocol == protocol)
                {
                    return sipChannel;
                }
            }

            return null;
        }

        #region Logging and metrics..

        private void FireSIPRequestInTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPRequest sipRequest)
        {
            try
            {
                if (SIPRequestInTraceEvent != null)
                {
                    SIPRequestInTraceEvent(protocol, localEndPoint, fromEndPoint, sipRequest);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPRequestInTraceEvent. " + excp.Message);
            }
        }

        private void FireSIPRequestOutTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPRequest sipRequest)
        {
            try
            {
                if (SIPRequestOutTraceEvent != null)
                {
                    SIPRequestOutTraceEvent(protocol, localEndPoint, toEndPoint, sipRequest);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPRequestOutTraceEvent. " + excp.Message);
            }
        }

        private void FireSIPResponseInTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPResponse sipResponse)
        {
            try
            {
                if (SIPResponseInTraceEvent != null)
                {
                    SIPResponseInTraceEvent(protocol, localEndPoint, fromEndPoint, sipResponse);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPResponseInTraceEvent. " + excp.Message);
            }
        }

        private void FireSIPResponseOutTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPResponse sipResponse)
        {
            try
            {
                if (SIPResponseOutTraceEvent != null)
                {
                    SIPResponseOutTraceEvent(protocol, localEndPoint, toEndPoint, sipResponse);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPResponseOutTraceEvent. " + excp.Message);
            }
        }

        private void FireSIPBadRequestInTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, string message, SIPValidationError error)
        {
            try
            {
                if (SIPBadRequestInTraceEvent != null)
                {
                    SIPBadRequestInTraceEvent(protocol, localEndPoint, fromEndPoint, message, error);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPBadRequestInTraceEvent. " + excp.Message);
            }
        }

        private void FireSIPBadResponseInTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, string message, SIPResponseParserError error)
        {
            try
            {
                if (SIPBadResponseInTraceEvent != null)
                {
                    SIPBadResponseInTraceEvent(protocol, localEndPoint, fromEndPoint, message, error);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPBadResponseInTraceEvent. " + excp.Message);
            }
        }

        private void RecordDiscardMetric(IPEndPoint remoteEndPoint)
        {
            SIPTransportMetric metric = new SIPTransportMetric(DateTime.Now, remoteEndPoint, SIPMessageTypesEnum.Unknown, SIPMethodsEnum.UNKNOWN, false, false, false, true, false, false, 0);
            AddMetricMeasurement(metric);
        }

        private void RecordSIPMessageMetric(IPEndPoint remoteEndPoint, SIPMessageTypesEnum messageType, SIPMethodsEnum method, DateTime startParseTime)
        {
            SIPTransportMetric metric = new SIPTransportMetric(DateTime.Now, remoteEndPoint, messageType, method, false, false, false, false, false, false, DateTime.Now.Subtract(startParseTime).TotalMilliseconds);
            AddMetricMeasurement(metric);
        }

        private void RecordSTUNMessageMetric(IPEndPoint remoteEndPoint, DateTime startParseTime)
        {
            SIPTransportMetric metric = new SIPTransportMetric(DateTime.Now, remoteEndPoint, SIPMessageTypesEnum.Unknown, SIPMethodsEnum.UNKNOWN, true, false, false, false, false, false, DateTime.Now.Subtract(startParseTime).TotalMilliseconds);
            AddMetricMeasurement(metric);
        }

        private void RecordBadSIPMessageMetric(IPEndPoint remoteEndPoint, DateTime startParseTime)
        {
            SIPTransportMetric metric = new SIPTransportMetric(DateTime.Now, remoteEndPoint, SIPMessageTypesEnum.Unknown, SIPMethodsEnum.UNKNOWN, false, false, true, false, false, false, DateTime.Now.Subtract(startParseTime).TotalMilliseconds);
            AddMetricMeasurement(metric);
        }

        private void RecordUnrecognisedMessageMetric(IPEndPoint remoteEndPoint, DateTime startParseTime)
        {
            SIPTransportMetric metric = new SIPTransportMetric(DateTime.Now, remoteEndPoint, SIPMessageTypesEnum.Unknown, SIPMethodsEnum.UNKNOWN, false, true, false, false, false, false, DateTime.Now.Subtract(startParseTime).TotalMilliseconds);
            AddMetricMeasurement(metric);
        }
        
        private void RecordTooLargeMessageMetric(IPEndPoint remoteEndPoint, DateTime startParseTime)
        {
            SIPTransportMetric metric = new SIPTransportMetric(DateTime.Now, remoteEndPoint, SIPMessageTypesEnum.Unknown, SIPMethodsEnum.UNKNOWN, false, false, false, false, true, false, DateTime.Now.Subtract(startParseTime).TotalMilliseconds);
            AddMetricMeasurement(metric);
        }

        private void RecordSIPMessageSendMetric(IPEndPoint remoteEndPoint, SIPMessageTypesEnum messageType, SIPMethodsEnum method)
        {
            SIPTransportMetric metric = new SIPTransportMetric(DateTime.Now, remoteEndPoint, messageType, method, false, false, false, false, false, true, 0);
            AddMetricMeasurement(metric);
        }

        private void AddMetricMeasurement(SIPTransportMetric metric)
        {
            lock (m_sipTransportMeasurements)
            {
                if (m_sipTransportMeasurements.Count < MAX_MEASUREMENTSQUEUE_SIZE)
                {
                    m_sipTransportMeasurements.Enqueue(metric);
                }
                else
                {
                    logger.Warn("SIPTransport metric not recorded as metrics maximum queue size of " + MAX_MEASUREMENTSQUEUE_SIZE + " has been reached.");
                }
            }
        }

        #endregion

        public SIPTransaction GetTransaction(string transactionId)
        {
            return m_transactionEngine.GetTransaction(transactionId);
        }

        public SIPTransaction GetTransaction(SIPRequest sipRequest)
        {
            return m_transactionEngine.GetTransaction(sipRequest);
        }

        public SIPNonInviteTransaction CreateNonInviteTransaction(SIPRequest sipRequest, IPEndPoint dstEndPoint, IPEndPoint sendFromEndPoint, SIPProtocolsEnum protocol)
        {
            try
            {
                SIPNonInviteTransaction nonInviteTransaction = new SIPNonInviteTransaction(this, sipRequest, dstEndPoint, sendFromEndPoint, protocol);

                if (nonInviteTransaction.TransactionCreationError == SIPValidationError.None)
                {
                    m_transactionEngine.AddTransaction(nonInviteTransaction);
                }

                return nonInviteTransaction;
            }
            catch (Exception excp)
            {
                logger.Error("Exception CreateNonInviteTransaction. " + excp.Message);
                return null;
            }
        }

        public UACInviteTransaction CreateUACTransaction(SIPRequest sipRequest, IPEndPoint dstEndPoint, IPEndPoint sendFromEndPoint, SIPProtocolsEnum protocol)
        {
            try
            {
                UACInviteTransaction uacInviteTransaction = new UACInviteTransaction(this, sipRequest, dstEndPoint, sendFromEndPoint, protocol);

                if (uacInviteTransaction.TransactionCreationError == SIPValidationError.None)
                {
                    m_transactionEngine.AddTransaction(uacInviteTransaction);
                }

                return uacInviteTransaction;
            }
            catch (Exception excp)
            {
                logger.Error("Exception CreateUACTransaction. " + excp.Message);
                return null;
            }
        }

        public UASInviteTransaction CreateUASTransaction(SIPRequest sipRequest, IPEndPoint dstEndPoint, IPEndPoint sendFromEndPoint, SIPProtocolsEnum protocol, bool authenticationRequired, string realm)
        {
            try
            {
                UASInviteTransaction uasInviteTransaction = new UASInviteTransaction(this, sipRequest, dstEndPoint, sendFromEndPoint, protocol, authenticationRequired, realm);

                if (uasInviteTransaction.TransactionCreationError == SIPValidationError.None)
                {
                    m_transactionEngine.AddTransaction(uasInviteTransaction);
                }

                return uasInviteTransaction;
            }
            catch (Exception excp)
            {
                logger.Error("Exception CreateUASTransaction. " + excp.Message);
                return null;
            }
        }

        public SIPCancelTransaction CreateCancelTransaction(SIPRequest sipRequest, IPEndPoint dstEndPoint, IPEndPoint sendFromEndPoint, SIPProtocolsEnum protocol, UASInviteTransaction inviteTransaction)
        {
            try
            {
                SIPCancelTransaction cancelTransaction = new SIPCancelTransaction(this, sipRequest, dstEndPoint, sendFromEndPoint, protocol, inviteTransaction);

                if (cancelTransaction.TransactionCreationError == SIPValidationError.None)
                {
                    m_transactionEngine.AddTransaction(cancelTransaction);
                }

                return cancelTransaction;
            }
            catch (Exception excp)
            {
                logger.Error("Exception CreateUASTransaction. " + excp.Message);
                return null;
            }
        }
    }
}
